﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using SlimDX.Direct3D10;
using SlimDX.DXGI;

namespace System.Windows.Media.Media3D10
{
    /// <summary>
    /// Modèle 3D pour une sphère
    /// </summary>
    public sealed class SphereModel3D10 : RenderModel3D10
    {
        #region Dependency Properties
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="Materials"/>.
        /// </summary>
        public static readonly DependencyProperty MaterialsProperty =
            DependencyProperty.Register("Materials", typeof(MaterialCollection), typeof(SphereModel3D10),
            new PropertyMetadata(new PropertyChangedCallback(Materials_PropertyChanged)));

        /// <summary>
        /// Identifie la propriété de dépendance <see cref="Radius"/>.
        /// </summary>
        public static readonly DependencyProperty RadiusProperty =
            DependencyProperty.Register("Radius", typeof(float), typeof(SphereModel3D10),
            new PropertyMetadata(new PropertyChangedCallback(Radius_PropertyChanged)));

        /// <summary>
        /// Identifie la propriété de dépendance <see cref="TesselationLevel"/>.
        /// </summary>
        public static readonly DependencyProperty TesselationLevelProperty =
            DependencyProperty.Register("TesselationLevel", typeof(int), typeof(SphereModel3D10),
            new PropertyMetadata(new PropertyChangedCallback(TesselationLevel_PropertyChanged)));
        #endregion

        #region Dependency Properties Callbacks
        private static void Materials_PropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            if (object.ReferenceEquals(sender, null))
                throw new ArgumentNullException("sender");
            if (object.ReferenceEquals(e, null))
                throw new ArgumentNullException("e");
            (sender as SphereModel3D10).OnMaterialsChanged(e);
        }

        private static void Radius_PropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            if (object.ReferenceEquals(sender, null))
                throw new ArgumentNullException("sender");
            if (object.ReferenceEquals(e, null))
                throw new ArgumentNullException("e");
            (sender as SphereModel3D10).OnRadiusChanged(e);
        }

        private static void TesselationLevel_PropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            if (object.ReferenceEquals(sender, null))
                throw new ArgumentNullException("sender");
            if (object.ReferenceEquals(e, null))
                throw new ArgumentNullException("e");
            (sender as SphereModel3D10).OnTesselationLevelChanged(e);
        }
        #endregion

        #region Constructors
        /// <summary>
        /// Initialise une nouvelle instance de la classe <see cref="SphereModel3D10"/>
        /// </summary>
        public SphereModel3D10()
            : base()
        {
            this.Materials = new MaterialCollection();
        }
        #endregion

        #region Data
        private const BaseMesh TESSELATION_BASE_MESH = BaseMesh.Octahedron;
        private MeshGeometry3D10 _geometry;
        #endregion

        #region Public Properties
        /// <summary>
        /// Liste des matériaux à appliquer
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227")]
        public MaterialCollection Materials
        {
            get { return (MaterialCollection)GetValue(MaterialsProperty); }
            set { SetValue(MaterialsProperty, value); }
        }

        /// <summary>
        /// Rayon de la sphère
        /// </summary>
        public float Radius
        {
            get { return (float)GetValue(RadiusProperty); }
            set { SetValue(RadiusProperty, value); }
        }

        /// <summary>
        /// Nombre de tesselations à appliquer lors de la tesselation
        /// </summary>
        public int TesselationLevel
        {
            get { return (int)GetValue(TesselationLevelProperty); }
            set { SetValue(TesselationLevelProperty, value); }
        }
        #endregion

        #region Private Members
        private void OnMaterialsChanged(DependencyPropertyChangedEventArgs e)
        {
            if (object.ReferenceEquals(e, null))
                throw new ArgumentNullException("e");
            if (this.Effect != null) this.Materials.SetEffect(this.Effect);
        }

        private void OnRadiusChanged(DependencyPropertyChangedEventArgs e)
        {
            if (object.ReferenceEquals(e, null))
                throw new ArgumentNullException("e");
            this.Tesselate();
        }

        private void OnTesselationLevelChanged(DependencyPropertyChangedEventArgs e)
        {
            if (object.ReferenceEquals(e, null))
                throw new ArgumentNullException("e");
            this.Tesselate();
        }

        private void Tesselate()
        {
            if (this.TesselationLevel < 1 || this.TesselationLevel > 7 - (int)TESSELATION_BASE_MESH)
                return;

            //this._geometry = SphereTesselator.TesselateLongLat(this.Radius, this.TesselationLevel, this.TesselationLevel);
            this._geometry = SphereTesselator.TesselateGeodesic(this.Radius, this.TesselationLevel, TESSELATION_BASE_MESH);
            this._geometry.Freeze();

            this.InvalidateVertexBufferBinding();
        }
        #endregion

        #region Model3D10 Overrides
        /// <summary>
        /// Appelée lors du changement de l'effet
        /// </summary>
        /// <param name="e"><see cref="DependencyPropertyChangedEventArgs"/> décrivant les informations sur le changement.</param>
        protected override void OnEffectChanged(DependencyPropertyChangedEventArgs e)
        {
            base.OnEffectChanged(e);
            if (this.Materials != null) this.Materials.SetEffect(this.Effect);
        }

        /// <summary>
        /// En cas de substitution dans une classe dérivée, participe au dessin des objets dans le pipeline DirectX10
        /// </summary>
        /// <param name="context">Contexte de dessin.</param>
        /// <exception cref="ArgumentNullException">Soulevée si le contexte est null.</exception>
        protected override void OnDraw(DrawingContext context)
        {
            if (object.ReferenceEquals(context, null))
                throw new ArgumentNullException("context");

            base.OnDraw(context);
            
            foreach (IDrawable material in this.Materials) material.Draw(context);

            context.DrawIndexed(this, PrimitiveTopology.TriangleList, _geometry.TriangleIndices);
        }
        #endregion

        #region GeometryModel3D10Base Implementation
        /// <summary>
        /// Crée le <see cref="InputLayout"/> associé à ce <see cref="RenderModel3D10"/>
        /// </summary>
        /// <returns></returns>
        protected override InputLayout CreateInputLayoutCore()
        {
            return this.CurrentEffectTechnique == null || _geometry == null ? 
                null : 
                this.CurrentEffectTechnique.CreateInputLayout(_geometry.GetInputElements());
        }

        /// <summary>
        /// Crée le <see cref="VertexBufferBinding"/> associé à ce <see cref="RenderModel3D10"/>
        /// </summary>
        /// <returns></returns>
        protected override VertexBufferBinding[] CreateVertexBufferBindingCore()
        {
            return _geometry == null || !_geometry.IsFrozen ? null : _geometry.GetVertexBufferBinding();
        }
        #endregion

        #region Freezable Implementation
        /// <summary>
        /// Une fois implémentée dans une classe dérivée, initialise une nouvelle instance de la classe dérivée <see cref="Freezable"/>. 
        /// </summary>
        /// <returns>Nouvelle instance.</returns>
        protected override Freezable CreateInstanceCore()
        {
            return new SphereModel3D10();
        }
        #endregion
    }
}
